
/*
 * 
 * @author Nebojsa Trninic
 * @date   29.09.2012.
 *
 * Examples of usage of Invoker of Background Commands, i.e. IBC when command encapsulates
 * net API. It is assumed that basic IBC tests from ibctest are successfull.
 *
 */


#include "ibc.h"

using namespace IBC;
using namespace std;

// In order to allow net API to be used, include:
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <errno.h>
#include <string.h>
#include <netdb.h>
#include <sys/types.h>
#include <netinet/in.h>
#include <sys/socket.h>
#include <arpa/inet.h>

#include "ex_beej/float/idl.h"




class CExceptionMyDiv0 : public exception
{
  virtual const char* what() const throw()
  {
    return "My Div by 0 exception happened";
  }

}; // class CExceptionMyDiv0


class CExceptionMyNet : public exception
{
public:
    CExceptionMyNet( const char *sErrDesc );
    ~CExceptionMyNet( ) throw() { if( m_sErrDesc != NULL ) { delete [] m_sErrDesc; m_sErrDesc=NULL; }};

  virtual const char* what() const throw()
  {
    return m_sErrDesc;
  }

private:
    char *m_sErrDesc;

}; // class CExceptionMyNet


CExceptionMyNet::CExceptionMyNet( const char *v_sErrDesc )
{
    if( v_sErrDesc != NULL )
    {
        int nLen = strlen ( v_sErrDesc ) + 1;
        m_sErrDesc = new char[ nLen ];
        strncpy( m_sErrDesc, v_sErrDesc, nLen );
    }
    else
        m_sErrDesc = NULL;

} // constructor CExceptionMyNet::CExceptionMyNet()


// More complex command CMyCommandNetDiv that accepts parameters and can be used as an example
// of different outcomes of operation execution: OK, ERR, EXCeption and TimeOut.
// Sleep time m_nST [seconds] introduced in order to be able to show timeout behaviour.
class CMyCommandNetDiv : public ICommand
{
  public:
    CMyCommandNetDiv( unsigned long v_nST ) : m_fA( 0.0 ), m_fB( 0.0 ), m_sAddr( NULL ),
                                              m_fRes( 0.0 ), m_nST( v_nST )
        {
            // state INIT from parent constructor
        }; // constructor CMyCommandNetDiv()

    ~CMyCommandNetDiv() { if( m_sAddr != NULL ) { delete [] m_sAddr; m_sAddr = NULL; } };

    void setParams( float v_fA, float v_fB, char *v_sAddr );

    virtual void execute( void ); // Calculates A/B by asking server to do the calc.
                                  // Does exception handling.

    float getResult( void ); // User should check state of command and only if S_EXEC_OK call this method.

  private:
    // Methods
    void NetCalc( void ); // Call the floating point server to perform the calc using values from attributes.

    // Parameters
    float m_fA;
    float m_fB;
    unsigned long m_nST; // Sleep time in seconds. Server is going to sleep it out, before sending
                         // the result back to client.
    char *m_sAddr; // Floating point server address.

    // Result/Error/Exception
    float m_fRes;
    // For the purpose of error, we are going to mark 24.0 / 3.0 = 8.0 as error.
    // Exception is handled in standard way in ICommand, execute() should allocate it. Division by
    // zero can be used as an example.

}; // CMyCommandNetDiv


void CMyCommandNetDiv::setParams( float v_fA, float v_fB,  char *v_sAddr )
{
    assert( m_nCommandState == CS_S_INIT );
            
    m_fA = v_fA;
    m_fB = v_fB;

    if( v_sAddr != NULL )
    {
        int nLen = strlen ( v_sAddr ) + 1;
        m_sAddr = new char[ nLen ];
        strncpy( m_sAddr, v_sAddr, nLen );
    }
    else
        m_sAddr = NULL;

    m_nCommandState = CS_S_PARAMSSET;

} // CMyCommandNetDiv::setParams()


// get sockaddr, IPv4 or IPv6:
void *get_in_addr(struct sockaddr *sa)
{
    if (sa->sa_family == AF_INET) {
        return &(((struct sockaddr_in*)sa)->sin_addr);
    }
    return &(((struct sockaddr_in6*)sa)->sin6_addr);
}


void CMyCommandNetDiv::NetCalc( void )
{
    assert( m_sAddr != NULL );
    assert( m_nCommandState == CS_S_PARAMSSET );

    int sockfd, numbytes;
    struct SNetData buf; // Input parameters that are going to be sent by client to the server.
    struct addrinfo hints, *servinfo, *p;
    int rv;
    char s[INET6_ADDRSTRLEN];

    memset(&hints, 0, sizeof hints);
    hints.ai_family = AF_UNSPEC;
    hints.ai_socktype = SOCK_STREAM;

    if ((rv = getaddrinfo(m_sAddr, PORT, &hints, &servinfo)) != 0)
    {
        string s( "getaddrinfo: " );
        s = s + gai_strerror( rv );
        throw CExceptionMyNet( s.c_str() );
    }

    // loop through all the results and connect to the first we can
    for(p = servinfo; p != NULL; p = p->ai_next)
    {
        if ((sockfd = socket(p->ai_family, p->ai_socktype,
                             p->ai_protocol)) == -1)
        {
            perror("client: socket");
            continue;
        }

        if (connect(sockfd, p->ai_addr, p->ai_addrlen) == -1)
        {
            close(sockfd);
            perror("client: connect");
            continue;
        }

        break;

    } // for

    if (p == NULL)
    {
        throw CExceptionMyNet( "client: failed to connect" );
    }

    inet_ntop(p->ai_family, get_in_addr((struct sockaddr *)p->ai_addr),
              s, sizeof s);

    cout << "client: connecting to " << s << endl;
    freeaddrinfo(servinfo); // all done with this structure

    // Fill in floating point server request structure.
    buf.fA = m_fA;
    buf.fB = m_fB;
    buf.nST = m_nST;

    // Ask the server to process the request.
    if (send( sockfd, &buf, sizeof buf, 0) != sizeof buf)
        perror("processClientRequest() send result problem");

    // Receive the result from server.
    if ((numbytes = recv(sockfd, &m_fRes, sizeof m_fRes, 0)) != sizeof m_fRes)
        perror("processClientRequest() recv result problem");

    cout << "after all client: m_fRes " << m_fRes << endl;

    close(sockfd);

} // CMyCommandNetDiv::NetCalc()


void CMyCommandNetDiv::execute( void )
{
    try
    {
        cout << "CMyCommandNetDiv::execute() begin" << endl;
        // More complex commands could check if parameters have been set and throw exception if not, or
        // mark it as an error.
        assert( m_nCommandState == CS_S_PARAMSSET );

        // For the purpose of timeout demo, attribute m_nST is sleep time. It is passed to Server
        // which does the math, sleeps and returns result.
        
        // Perform calculation by calling the server through the network.
        NetCalc(); // Can throw CExceptionMyNet in case of net problems.

        cout << "******* " << m_fA << " / " << m_fB << " = " << m_fRes << endl;

        // Since debugging showed that 1.0 / 0.0 = inf without an exception, we raise one in need of it.
        if( m_fB == 0.0 )
        {
            CExceptionMyDiv0 eobj;
            throw eobj;
        }

        if( m_fRes == 8.0 ) // E.g. 24.0 / 3.0 = 8.0
            m_nCommandState = CS_E_EXEC_ERR; // Internal decision to mark it as an error for demo purposes.
        else
            m_nCommandState = CS_S_EXEC_OK; // Demo of good results.
    }
    catch( CExceptionMyNet &e )
    {
        assert( e.what() != NULL );
        cerr << "exception caught: " << e.what() << endl;
        if( m_pExc == NULL )
        {
            m_pExc = new CExceptionMyNet( e.what() );   // This one is released in command destructor.
            m_nCommandState = CS_E_EXEC_EXC;
        }
    }
    catch( exception &e )
    {
        cerr << "exception caught: " << e.what() << endl;
        if( m_pExc == NULL )
        {
            m_pExc = new CExceptionMyDiv0;   // This one is released in command destructor.
            m_nCommandState = CS_E_EXEC_EXC; // Div by 0 can be demo of exception behaviour.
        }
    } // catch

    cout << "CMyCommandNetDiv::execute() end" << endl;

} // CMyCommandNetDiv::execute()


float CMyCommandNetDiv::getResult( void )
{
    assert( m_nCommandState == CS_S_EXEC_OK ); // Other commands may decide differently
                                               // and allow this method also in case of error.

    return m_fRes;

} // CMyCommandNetDiv::getResult()


int main( int argc, char *argv[] )
{
    cout << "******* IBC start" << endl;

    if (argc != 2)
    {
        cerr << "usage: ibcnettest hostname" << endl;
        exit(1);
    }

    char *sAddr = argv[1]; // Address of floating point server.

    assert( sAddr != NULL );
    cout << "All commands are going to be directed to floating point server @ " << sAddr << endl;

    cout << "******* Net command demo OK" << endl;
    {
        CMyCommandNetDiv *pCmd = new CMyCommandNetDiv( 0 ); // 99 * 99 = 9801, do not sleep.
        pCmd->setParams( 9801.0, 99.0, sAddr );

        CInvokerBackgroundCommands *pInv = new CInvokerBackgroundCommands( 1, // 1s timeout.
                                                                           CInvokerBackgroundCommands::TOP_LINGER,
                                                                           pCmd );

        assert( pInv->getInvokerState() == CInvokerBackgroundCommands::IS_S_PENDING );

        pInv->execCmdInBgThr();

        assert( pInv->getInvokerState() == CInvokerBackgroundCommands::IS_S_DONE );
        assert( pInv->RCget() == 1 ); // only Invoker referencing, Bg thread is gone by now. Safe to access
                                      // Invoker command without locking, i.e. pCmd.
        assert( pCmd->getException() == NULL );
        assert( pCmd->getCommandState() == ICommand::CS_S_EXEC_OK );

        float fRes = pCmd->getResult();
        assert( fRes == 99.0 );

        cout << "******* Command result is " << fRes << endl;

        pInv->RCdec(); // resource deallocation follows.

    } // command OK

    cout << "******* Net command demo ERROR" << endl;
    {
        CMyCommandNetDiv *pCmd = new CMyCommandNetDiv( 0 ); // class is such that 24.0 / 3.0 = 8.0 is error. No sleep.
        pCmd->setParams( 24.0, 3.0, sAddr );

        CInvokerBackgroundCommands *pInv = new CInvokerBackgroundCommands( 1, // timeout 1s
                                                                           CInvokerBackgroundCommands::TOP_LINGER,
                                                                           pCmd );

        assert( pInv->getInvokerState() == CInvokerBackgroundCommands::IS_S_PENDING );

        pInv->execCmdInBgThr();

        assert( pInv->getInvokerState() == CInvokerBackgroundCommands::IS_S_DONE );
        assert( pInv->RCget() == 1 ); // only Invoker referencing, Bg thread is gone by now. Safe to access
                                      // Invoker command without locking, i.e. pCmd.
        assert( pCmd->getException() == NULL );
        assert( pCmd->getCommandState() == ICommand::CS_E_EXEC_ERR );

        // float fRes = pCmd->getResult(); must not obtain command result, since it asserts
        // command state S_EXEC_OK
        // assert( fRes == 8.0 );

        cout << "******* Command in ERROR end." << endl;

        pInv->RCdec(); // resource deallocation follows.

    } // command ERROR

    cout << "******* Net command demo EXCEPTION" << endl;
    {
        CMyCommandNetDiv *pCmd = new CMyCommandNetDiv( 0 ); // Divide by 0 is exception. No sleep.
        pCmd->setParams( 1.0, 0.0, sAddr );

        CInvokerBackgroundCommands *pInv = new CInvokerBackgroundCommands( 1, // timeout 1s
                                                                           CInvokerBackgroundCommands::TOP_LINGER,
                                                                           pCmd );

        assert( pInv->getInvokerState() == CInvokerBackgroundCommands::IS_S_PENDING );

        pInv->execCmdInBgThr();

        assert( pInv->getInvokerState() == CInvokerBackgroundCommands::IS_S_DONE );
        assert( pInv->RCget() == 1 ); // only Invoker referencing, Bg thread is gone by now. Safe to access
                                      // Invoker command without locking, i.e. pCmd.
        assert( pCmd->getCommandState() == ICommand::CS_E_EXEC_EXC );
        exception *e = pCmd->getException();
        assert( e != NULL );

        cout << "******* Command exception is " << e->what() << endl;

        pInv->RCdec(); // resource deallocation follows.

    } // command EXCEPTION

    cout << "******* Timeout lingering demo" << endl;
    {
        // This one is the same as OK demo, but we tell the command to sleep for a time longer than timeout.

        CMyCommandNetDiv *pCmd = new CMyCommandNetDiv( 10 ); // 99 * 99 = 9801, sleep 10s.
        pCmd->setParams( 9801.0, 99.0, sAddr );

        CInvokerBackgroundCommands *pInv = new CInvokerBackgroundCommands( 1, // timeout 1s
                                                                           CInvokerBackgroundCommands::TOP_LINGER,
                                                                           pCmd );

        assert( pInv->getInvokerState() == CInvokerBackgroundCommands::IS_S_PENDING );

        pInv->execCmdInBgThr();

        assert( pInv->getInvokerState() == CInvokerBackgroundCommands::IS_E_TIMEOUT_LINGER );
        assert( pInv->RCget() == 2 ); // Both Invoker and lingering Bg thread are referencing. Not safe to access
                                      // Invoker command, i.e. pCmd, since Bg thread has it being executed.
        // assert( pCmd->getException() == NULL );
        // assert( pCmd->getCommandState() == ICommand::S_EXEC_OK );

        cout << "******* Command is still running in Bg thread, we do not touch it ";

        pInv->RCdec(); // Invoker dereferences, so RC should be 1 for the lingering Bg thread.
        assert( pInv->RCget() == 1 );

        // If we do not wait, main() finishes and Bg thread never gets to the finish point, it is killed when
        // main exits.

        // We can implement quasi busy waiting on ref count and then go on when Bg thread dereferences it to 0.
        // It is for further study to design a solution in which this can be performed differently, e.g. waiting on
        // a condition.
        if( 1 )
        {
            cout << "******* Quasi busy waiting" << endl;
            int i = 1;

            while( pInv->RCget() == 1 )
            {
                cout << "******* " << i++ << "s" << endl;
                CThread::sleep( 1, 0 ); // sleep 1s.
            }

            assert( pInv->RCget() == 0 ); // Bg thread finished.
        } // if wait for Bg thread to finish.
        else
        {
            cout << "******* NOT quasi busy waiting" << endl;
            assert( pInv->RCget() == 1 ); // Bg thread still running.
        }

    } // timeout linger demo.

    // Timeout kill demo not possible, since CThread does not allow thread to be killed.

    cout << "******* IBC end" << endl;

} // main()
